package com.atguigu.gmall.item.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.item.service.ItemApiService;
import com.atguigu.gmall.list.client.ListFeignClient;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
@SuppressWarnings("all")
public class ItemApiServiceImpl implements ItemApiService {
    //注入service-product-client里边的接口
    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private ListFeignClient listFeignClient;
    /**
     * 商品详情页汇总product模块中的数据
     * @param skuId
     * @return
     */
    @Override
    public Map<String, Object> selectBySkuId(Long skuId) {

        //创建封装数据的对象
        Map<String, Object> map = new HashMap<>();
        /**
         * //添加布隆过滤器
         * RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
         * //判断布隆过滤器中是否存在该id,不存在就不往下执行了.
         * if(!bloomFilter.contains(skuId)){
         *     return map;
         * }
         *
         */

        /**
         * 未加异步操作之前的代码,就是按照从上到下的顺序依次执行的
         * //根据skuId获取SkuInfo
         *         SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
         *         if(skuInfo != null){
         *             //根据三级分类id获取分类信息
         *             BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
         *             //根据spuId 获取海报数据
         *             List<SpuPoster> spuPosterBySpuId = productFeignClient.findSpuPosterBySpuId(skuInfo.getSpuId());
         *             //根据spuId获取到销售属性值Id与skuId组成的数据集
         *             Map skuValueIdsMap = productFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
         *             //根据spuId,skuId 获取销售属性数据,和选中状态
         *             List<SpuSaleAttr> spuSaleAttrListCheckBySku = productFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
         *             //进行数据封装
         *             map.put("categoryView",categoryView);
         *             map.put("spuPosterList",spuPosterBySpuId);
         *
         *             map.put("spuSaleAttrList",spuSaleAttrListCheckBySku);
         *
         *             //map.put("valuesSkuJson",skuValueIdsMap);
         *             map.put("valuesSkuJson", JSON.toJSONString(skuValueIdsMap));
         *         }
         *         //根据skuId 获取平台属性数据
         *         List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
         *         //因为前段页面中用thymeleaf获取的字段与接口返回实体类的数据不一致,所以这里用map进行数据封装在返回
         *         if(!CollectionUtils.isEmpty(attrList)){
         *             List<Map<String,String>> mapList = attrList.stream().map(attrInfo -> {
         *                 //获取属性名
         *                 String attrName = attrInfo.getAttrName();
         *                 //获取属性值
         *                 //这里是因为两张表的对应关系,一个属性名只对应一个属性值,后端给的是list集合,所以取第一个就可以了
         *                 String attrValue = attrInfo.getAttrValueList().get(0).getValueName();
         *                 //如上所示:前段只需要这两个信息,根据key进行的查询,在前端item的452行,有没有合适的实体类
         *                 //所以返回List<map>
         *                 Map<String, String> skuAttr = new HashMap<>();
         *                 skuAttr.put("attrName", attrName);
         *                 skuAttr.put("attrValue", attrValue);
         *                 return skuAttr;
         *             }).collect(Collectors.toList());
         *             map.put("skuAttrList",mapList);
         *         }
         *
         *         //根据skuId 获取最新的商品价格
         *         BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
         *
         *         map.put("skuInfo",skuInfo);
         *         map.put("price",skuPrice);
         *         return map;
         */

        /**
         * 加上异步操作,使查询效率更高
         * 类:CompletableFuture
         * 带有Async默认的是异步执行,所谓异步就是指不在当前线程内执行
         *
         * 创建异步对象:(静态方法)
         *  runAsync:没有返回值
         *  supplyAsync:有返回值
         *  (下边的都不是静态的了,通过异步对象调用)
         *  whenComplete:不是异步,顺序执行
         *  whenCompleteAsync:是异步,从线程池中获取线程执行
         *
         * 串行与并行
         *  thenApply:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值
         *  thenAccept:消费处理结果.接收任务的处理结果,并消费处理,无返回结果
         *  thenRun:只要上面的任务执行完成,就开始执行therun,只是处理任务后,执行theRun的后续操作
         *
         *
         * 多任务组合:静态方法
         * allOf:等待所有任务完成
         * anyOf:只要有一个任务完成
         */


        //根据skuId获取SkuInfo
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            map.put("skuInfo", skuInfo);
            return skuInfo;
        }, threadPoolExecutor);

        /**
         * 该方法需要依赖skuInfoFuture,所以用skuInfoFuture.thenAcceptAsync这种方式异步执行,但是要等
         * 上方的skuInfoFuture执行完之后才会执行,因为需要里边的属性
         */
        CompletableFuture<Void> categoryViewFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                //根据三级分类id获取分类信息
                BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
                map.put("categoryView", categoryView);
            }
        }, threadPoolExecutor);

        /**
         * 该方法需要依赖skuInfoFuture,所以用skuInfoFuture.thenAcceptAsync这种方式异步执行,但是要等
         * 上方的skuInfoFuture执行完之后才会执行,因为需要里边的属性
         */
        CompletableFuture<Void> spuPosterListFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                //根据spuId 获取海报数据
                List<SpuPoster> spuPosterBySpuId = productFeignClient.findSpuPosterBySpuId(skuInfo.getSpuId());
                map.put("spuPosterList", spuPosterBySpuId);
            }
        }, threadPoolExecutor);

        /**
         * 该方法需要依赖skuInfoFuture,所以用skuInfoFuture.thenAcceptAsync这种方式异步执行,但是要等
         * 上方的skuInfoFuture执行完之后才会执行,因为需要里边的属性
         */
        CompletableFuture<Void> valuesSkuJsonFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                //根据spuId获取到销售属性值Id与skuId组成的数据集
                Map skuValueIdsMap = productFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
                map.put("valuesSkuJson", JSON.toJSONString(skuValueIdsMap));
            }
        }, threadPoolExecutor);

        /**
         * 该方法需要依赖skuInfoFuture,所以用skuInfoFuture.thenAcceptAsync这种方式异步执行,但是要等
         * 上方的skuInfoFuture执行完之后才会执行,因为需要里边的属性
         */
        CompletableFuture<Void> spuSaleAttrListFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                //根据spuId,skuId 获取销售属性数据,和选中状态
                List<SpuSaleAttr> spuSaleAttrListCheckBySku = productFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
                map.put("spuSaleAttrList", spuSaleAttrListCheckBySku);
            }
        }, threadPoolExecutor);

        /**
         * 该方法不需要依赖skuInfoFuture,所以可以向skuInfoFuture这个一样的方式异步运行CompletableFuture.runAsync
         */
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            //根据skuId 获取平台属性数据
            List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
            //因为前端页面中用thymeleaf获取的字段与接口返回实体类的数据不一致,所以这里用map进行数据封装在返回
            if (!CollectionUtils.isEmpty(attrList)) {
                List<Map<String, String>> mapList = attrList.stream().map(attrInfo -> {
                    //获取属性名
                    String attrName = attrInfo.getAttrName();
                    //获取属性值
                    //这里是因为两张表的对应关系,一个属性名只对应一个属性值,后端给的是list集合,所以取第一个就可以了
                    String attrValue = attrInfo.getAttrValueList().get(0).getValueName();
                    //如上所示:前段只需要这两个信息,根据key进行的查询,在前端item的452行,有没有合适的实体类
                    //所以返回List<map>
                    Map<String, String> skuAttr = new HashMap<>();
                    skuAttr.put("attrName", attrName);
                    skuAttr.put("attrValue", attrValue);
                    return skuAttr;
                }).collect(Collectors.toList());
                map.put("skuAttrList", mapList);
            }
        }, threadPoolExecutor);

        /**
         * 该方法不需要依赖skuInfoFuture,所以可以向skuInfoFuture这个一样的方式异步运行CompletableFuture.runAsync
         */
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            //根据skuId 获取最新的商品价格
            BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
            map.put("price", skuPrice);
        }, threadPoolExecutor);

        CompletableFuture<Void> incrHotScoreFuture = CompletableFuture.runAsync(() -> {
            listFeignClient.incrHotScore(skuId);
        }, threadPoolExecutor);

        /**
         * 最后执行一个allOf静态方法,所有的异步都执行完成之后在返回数据
         */
        CompletableFuture.allOf(
                skuInfoFuture,
                categoryViewFuture,
                spuPosterListFuture,
                valuesSkuJsonFuture,
                spuSaleAttrListFuture,
                voidCompletableFuture,
                priceFuture,
                incrHotScoreFuture
        ).join();

        return map;
    }
}
